package com.huaboot.framework.log.aspect;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgentUtil;
import com.alibaba.fastjson.JSON;
import com.huaboot.commont.constant.Constant;
import com.huaboot.commont.utils.HttpContextUtils;
import com.huaboot.commont.utils.IpUtil;
import com.huaboot.framework.log.annotations.OperateLog;
import com.huaboot.framework.log.dto.OperateLogDto;
import com.huaboot.framework.log.service.OperateLogService;
import com.huaboot.framework.security.user.LoginUser;
import com.huaboot.framework.security.utils.SecurityUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.IntStream;

/**
 * 日志切面
 * description: OperateLogAspect
 * author: ff
 */
@Aspect
@Component
@AllArgsConstructor
public class OperateLogAspect {

    private final OperateLogService operateLogService;

    /**
     * 用于指示该方法将在目标方法执行前后进行拦截和增强
     * @param proceedingJoinPoint  是一个连接点对象，它封装了目标方法的信息，包括方法名、参数等。
     * @param operateLog 拦截注解
     * @return
     */
    @Around("@annotation(operateLog)")
    public Object around(ProceedingJoinPoint proceedingJoinPoint, OperateLog operateLog) throws Throwable {
        //拿到执行方法之前的时间
        LocalDateTime startTime = LocalDateTime.now();
        try {
            //执行逻辑，放行
            Object proceed = proceedingJoinPoint.proceed();
            //逻辑执行完日志插入
            saveLog(proceedingJoinPoint,operateLog,startTime, Constant.SUCCESS);
            return proceed;
        }catch (Exception e) {
            //切面拦截异常捕获处理
            saveLog(proceedingJoinPoint, operateLog, startTime, Constant.FAIL);
            throw e;
        }
    }

    /**
     * 插入日志操作
     * @param proceedingJoinPoint
     * @param operateLog
     * @param startTime
     */
    private void saveLog(ProceedingJoinPoint proceedingJoinPoint, OperateLog operateLog, LocalDateTime startTime, Integer status) {
        OperateLogDto log = new OperateLogDto();
        // 执行时长
        long duration = LocalDateTimeUtil.between(startTime, LocalDateTime.now()).toMillis();
        log.setDuration((int) duration);
        //操作用户信息
        LoginUser loginUser = SecurityUtil.getAuthentication();
        if (loginUser != null){
            log.setUserId(loginUser.getUserId());
            log.setRealName(loginUser.getRealName());
            log.setTenantId(loginUser.getTenantId());
        }
        // 操作类型
        log.setOperateType(operateLog.type()[0].getValue());
        // 设置module值
        log.setModule(operateLog.module());
        // 设置name值
        log.setName(operateLog.name());
        // 如果没有指定module值，则从tag读取
        if (StrUtil.isBlank(log.getModule())) {
            Tag tag = getClassAnnotation(proceedingJoinPoint, Tag.class);
            if (tag != null) {
                log.setModule(tag.name());
            }
        }

        // 如果没有指定name值，则从operation读取
        if (StrUtil.isBlank(log.getName())) {
            Operation operation = getMethodAnnotation(proceedingJoinPoint, Operation.class);
            if (operation != null) {
                log.setName(operation.summary());
            }
        }
        // 请求相关
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        if (request != null) {
            log.setIp(ServletUtil.getClientIP(request));
            log.setAddress(IpUtil.getAddressByIP(log.getIp()));
            log.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
            log.setReqUri(request.getRequestURI());
            log.setReqMethod(request.getMethod());
        }
        assert request != null;
        String uaStr = request.getHeader("user-agent");
        log.setBrowser(UserAgentUtil.parse(uaStr).getBrowser().toString());
        log.setOs(UserAgentUtil.parse(uaStr).getOs().toString());
        log.setReqParams(obtainMethodArgs(proceedingJoinPoint));
        log.setStatus(status);


        // 保存操作日志
        operateLogService.saveLog(log);

    }
    private String obtainMethodArgs(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] argNames = methodSignature.getParameterNames();
        Object[] argValues = joinPoint.getArgs();

        // 拼接参数
        Map<String, Object> args = MapUtil.newHashMap(argValues.length);
        for (int i = 0; i < argNames.length; i++) {
            String argName = argNames[i];
            Object argValue = argValues[i];
            args.put(argName, ignoreArgs(argValue) ? "[ignore]" : argValue);
        }
        return JSON.toJSONString(args);
    }

    private static boolean ignoreArgs(Object object) {
        Class<?> clazz = object.getClass();

        // 处理数组
        if (clazz.isArray()) {
            return IntStream.range(0, Array.getLength(object))
                    .anyMatch(index -> ignoreArgs(Array.get(object, index)));
        }

        // 处理集合
        if (Collection.class.isAssignableFrom(clazz)) {
            return ((Collection<?>) object).stream()
                    .anyMatch((Predicate<Object>) OperateLogAspect::ignoreArgs);
        }

        // 处理Map
        if (Map.class.isAssignableFrom(clazz)) {
            return ignoreArgs(((Map<?, ?>) object).values());
        }

        return object instanceof MultipartFile
                || object instanceof HttpServletRequest
                || object instanceof HttpServletResponse
                || object instanceof BindingResult;
    }

    private static <T extends Annotation> T getMethodAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
        return ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(annotationClass);
    }

    private static <T extends Annotation> T getClassAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
        return ((MethodSignature) joinPoint.getSignature()).getMethod().getDeclaringClass().getAnnotation(annotationClass);
    }
}
